package goeasymodbus

import (
	"bytes"
	"encoding/binary"
	"encoding/hex"
	"fmt"
)

const (
	ModBusRTU   = "rtu"
	ModBusTCP   = "tcp"
	ModBusASCII = "ascii"
)

// protocol frame: asciiStart + ( slaveID + functionCode + data + lrc ) + CR + LF.
const (
	asciiStart = ":"
	asciiEnd   = "\r\n"
	hexTable   = "0123456789ABCDEF"
)

// proto address limit.
const (
	AddressBroadCast = 0
	AddressMin       = 1
	AddressMax       = 247
)

const (
	pduMinSize = 1   // funcCode(1)
	pduMaxSize = 253 // funcCode(1) + data(252)

	rtuAduMinSize = 4   // address(1) + funcCode(1) + crc(2)
	rtuAduMaxSize = 256 // address(1) + PDU(253) + crc(2)

	asciiAduMinSize       = 3
	asciiAduMaxSize       = 256
	asciiCharacterMaxSize = 513

	tcpProtocolIdentifier = 0x0000
	// Modbus Application Protocol
	tcpHeaderMbapSize = 7 // MBAP header
	tcpAduMinSize     = 8 // MBAP + funcCode
	tcpAduMaxSize     = 260
)

// proto register limit
const (
	// Bits
	ReadBitsQuantityMin  = 1    // 0x0001
	ReadBitsQuantityMax  = 2000 // 0x07d0
	WriteBitsQuantityMin = 1    // 1
	WriteBitsQuantityMax = 1968 // 0x07b0
	// 16 Bits
	ReadRegQuantityMin             = 1   // 1
	ReadRegQuantityMax             = 125 // 0x007d
	WriteRegQuantityMin            = 1   // 1
	WriteRegQuantityMax            = 123 // 0x007b
	ReadWriteOnReadRegQuantityMin  = 1   // 1
	ReadWriteOnReadRegQuantityMax  = 125 // 0x007d
	ReadWriteOnWriteRegQuantityMin = 1   // 1
	ReadWriteOnWriteRegQuantityMax = 121 // 0x0079
)

// Function Code
const (
	// Bit access
	FuncCodeReadCoils          = 1
	FuncCodeReadDiscreteInputs = 2
	FuncCodeWriteSingleCoil    = 5

	// 16-bit access
	FuncCodeReadHoldingRegisters   = 3
	FuncCodeReadInputRegisters     = 4
	FuncCodeWriteSingleRegister    = 6
	FuncCodeWriteMultipleCoil      = 15
	FuncCodeWriteMultipleRegisters = 16
)

// Exception Code
const (
	ExceptionCodeIllegalFunction                    = 1
	ExceptionCodeIllegalDataAddress                 = 2
	ExceptionCodeIllegalDataValue                   = 3
	ExceptionCodeServerDeviceFailure                = 4
	ExceptionCodeAcknowledge                        = 5
	ExceptionCodeServerDeviceBusy                   = 6
	ExceptionCodeNegativeAcknowledge                = 7
	ExceptionCodeMemoryParityError                  = 8
	ExceptionCodeGatewayPathUnavailable             = 10
	ExceptionCodeGatewayTargetDeviceFailedToRespond = 11
)

// ExceptionError implements error interface.
type ExceptionError struct {
	ExceptionCode byte
}

// Error converts known modbus exception code to error message.
func (e *ExceptionError) Error() string {
	var name string
	switch e.ExceptionCode {
	case ExceptionCodeIllegalFunction:
		name = "illegal function"
	case ExceptionCodeIllegalDataAddress:
		name = "illegal data address"
	case ExceptionCodeIllegalDataValue:
		name = "illegal data value"
	case ExceptionCodeServerDeviceFailure:
		name = "server device failure"
	case ExceptionCodeAcknowledge:
		name = "acknowledge"
	case ExceptionCodeServerDeviceBusy:
		name = "server device busy"
	case ExceptionCodeNegativeAcknowledge:
		name = "Negative Acknowledge"
	case ExceptionCodeMemoryParityError:
		name = "memory parity error"
	case ExceptionCodeGatewayPathUnavailable:
		name = "gateway path unavailable"
	case ExceptionCodeGatewayTargetDeviceFailedToRespond:
		name = "gateway target device failed to respond"
	default:
		name = "unknown"
	}
	return fmt.Sprintf("modbus: exception '%v' (%s)", e.ExceptionCode, name)
}

// protocolTCPHeader independent of underlying communication layers.
type protocolTCPHeader struct {
	transactionID uint16
	protocolID    uint16
	length        uint16
	slaveID       uint8 // only modbus RTU and ascii
}

// ProtocolDataUnit (PDU) is independent of underlying communication layers.
type ProtocolDataUnit struct {
	FuncCode byte
	Data     []byte
}

// protocolFrame protocol frame in pool
type protocolFrame struct {
	adu []byte
}

type ProtocolJsonStruct struct {
	ModbusType     string
	DeviceSN       string
	SlaveID        byte     `json:"slaveID"`
	FuncCode       byte     `json:"funcCode"`
	Register       uint16   `json:"register`
	RegisterLength uint16   `json:"registerLength"`
	WRData         []uint16 `json:"wrData"`
}

type ProtocolParser struct {
	CounterTid   uint16
	TcpHead      protocolTCPHeader
	Verify       bool
	ProtocolJson *ProtocolJsonStruct
	Pdu          *ProtocolDataUnit
}

func NewProtocolParser(pjs ProtocolJsonStruct) *ProtocolParser {
	switch pjs.ModbusType {
	case ModBusTCP:
		pp := ProtocolParser{
			CounterTid:   0,
			TcpHead:      protocolTCPHeader{transactionID: 0, protocolID: 0, length: 0, slaveID: pjs.SlaveID},
			Verify:       false,
			ProtocolJson: &pjs,
			Pdu:          &ProtocolDataUnit{FuncCode: pjs.FuncCode, Data: make([]byte, tcpAduMaxSize)},
		}
		return &pp
	case ModBusRTU:
		pp := ProtocolParser{
			Verify:       false,
			ProtocolJson: &pjs,
			Pdu:          &ProtocolDataUnit{FuncCode: pjs.FuncCode, Data: make([]byte, rtuAduMaxSize)},
		}
		return &pp
	case ModBusASCII:
		pp := ProtocolParser{
			Verify:       false,
			ProtocolJson: &pjs,
			Pdu:          &ProtocolDataUnit{FuncCode: pjs.FuncCode, Data: make([]byte, asciiAduMaxSize)},
		}
		return &pp
	default:
		return nil
	}
}

func (pp *ProtocolParser) EncodeJsonToModbusFrame() ([]byte, error) {

	var err error
	var pdu ProtocolDataUnit
	sf := protocolFrame{adu: make([]byte, rtuAduMaxSize)}
	psd := pp.ProtocolJson
	switch {
	case psd.FuncCode >= FuncCodeReadCoils && psd.FuncCode <= FuncCodeWriteSingleRegister:
		if psd.WRData == nil {
			return nil, fmt.Errorf("modbus: write data is nil")
		}
		if (len(psd.WRData) != 1) || psd.RegisterLength != 1 {
			return nil, fmt.Errorf("modbus: funcCode '%v' write data and length must be 1", psd.FuncCode)
		}
		pdu = ProtocolDataUnit{FuncCode: psd.FuncCode, Data: make([]byte, 4)}
		pdu.Data[0] = byte(psd.Register >> 8)
		pdu.Data[1] = byte(psd.Register)
		pdu.Data[2] = byte(psd.WRData[0] >> 8)
		pdu.Data[3] = byte(psd.WRData[0])
	case psd.FuncCode == FuncCodeWriteMultipleCoil:
		if psd.WRData == nil {
			return nil, fmt.Errorf("modbus: write data is nil")
		}
		if psd.RegisterLength != uint16(len(psd.WRData)) {
			return nil, fmt.Errorf("modbus: funcCode '%v' write data and length must be equal", psd.FuncCode)
		}
		if (psd.RegisterLength > WriteBitsQuantityMax) || (psd.RegisterLength < WriteBitsQuantityMin) {
			return nil, fmt.Errorf("modbus: funcCode '%v' write data and length must be in range [%v,%v]", psd.FuncCode, WriteBitsQuantityMin, WriteBitsQuantityMax)
		}
		pdu = ProtocolDataUnit{FuncCode: psd.FuncCode, Data: make([]byte, 6)}
		pdu.Data[0] = byte(psd.Register >> 8)
		pdu.Data[1] = byte(psd.Register)
		pdu.Data[2] = byte(psd.RegisterLength >> 8)
		pdu.Data[3] = byte(psd.RegisterLength)
		pdu.Data[4] = byte(len(psd.WRData))
		for i := 0; i < int(psd.RegisterLength); i++ {
			pdu.Data[5+i] = byte(psd.WRData[i])
		}
	case psd.FuncCode == FuncCodeWriteMultipleRegisters:
		if (psd.RegisterLength > ReadWriteOnWriteRegQuantityMax) || (psd.RegisterLength < ReadWriteOnWriteRegQuantityMin) {
			return nil, fmt.Errorf("modbus: register length '%v' is out of range", psd.RegisterLength)
		}
		if len(psd.WRData) != int(psd.RegisterLength) {
			return nil, fmt.Errorf("modbus: register length '%v'!= wrdada length '%v'", psd.RegisterLength, len(psd.WRData))
		}
		pdu = ProtocolDataUnit{FuncCode: psd.FuncCode, Data: make([]byte, 5+2*psd.RegisterLength)}
		pdu.Data[0] = byte(psd.Register >> 8)
		pdu.Data[1] = byte(psd.Register)
		pdu.Data[2] = byte(psd.RegisterLength >> 8)
		pdu.Data[3] = byte(psd.RegisterLength)
		pdu.Data[4] = byte(psd.RegisterLength)
		for i := byte(0); i < byte(2*psd.RegisterLength); i++ {
			pdu.Data[5+i] = byte(psd.WRData[i] >> 8)
			pdu.Data[6+i] = byte(psd.WRData[i])
		}
	default:
		return nil, fmt.Errorf("modbus: funcCode '%v' out of range [1,2,3,4,5,6,16]", psd.FuncCode)
	}
	switch pp.ProtocolJson.ModbusType {
	case ModBusASCII:
		sf.adu, err = sf.encodeASCIIFrame(psd.SlaveID, pdu)
	case ModBusRTU:
		sf.adu, err = sf.encodeRTUFrame(psd.SlaveID, pdu)
	case ModBusTCP:
		// head := protocolTCPHeader{transactionID: tcpCounterTid, protocolID: 0, length: uint16(len(pdu.Data) + 1), slaveID: psd.SlaveID}
		pp.TcpHead, sf.adu, err = sf.encodeTCPFrame(pp.CounterTid, psd.SlaveID, pdu)
		pp.CounterTid++
	default:
		err = fmt.Errorf("modbus: modbus type '%v' is not supported", pp.ProtocolJson.ModbusType)
	}
	if (len(pdu.Data) < pduMinSize) || (len(pdu.Data) > pduMaxSize) {
		return nil, fmt.Errorf("modbus: pdu data length '%v' is out of range", len(pdu.Data))
	}
	pp.Pdu = &pdu
	return sf.adu, err
}
func (pp *ProtocolParser) DecodeModbusFrameToJson(adu []byte) (ProtocolJsonStruct, error) {

	var err error
	var slaveID uint8
	var head protocolTCPHeader
	var pdu = make([]byte, ReadRegQuantityMax)
	var pjs = ProtocolJsonStruct{
		ModbusType: pp.ProtocolJson.ModbusType,
		WRData:     make([]uint16, pp.ProtocolJson.RegisterLength),
	}

	switch pp.ProtocolJson.ModbusType {
	case ModBusASCII:
		slaveID, pdu, err = decodeASCIIFrame(adu)
	case ModBusRTU:
		slaveID, pdu, err = decodeRTUFrame(adu)
	case ModBusTCP:
		head, pdu, err = decodeTCPFrame(adu)
		//判断发送的TCP头和接收的TCP头是否一致
		switch {
		case head.transactionID != pp.TcpHead.transactionID: //判断事务ID是否一致
			pp.Verify = false
			err = fmt.Errorf("modbus: transactionID '%v' is not equal to '%v'", head.transactionID, pp.TcpHead.transactionID)
		case head.protocolID != pp.TcpHead.protocolID: //判断协议ID是否一致 0:modbus
			pp.Verify = false
			err = fmt.Errorf("modbus: protocolID '%v' is not equal to '%v'", head.protocolID, pp.TcpHead.protocolID)
		case head.slaveID != pp.TcpHead.slaveID: //判断从站ID是否一致
			pp.Verify = false
			err = fmt.Errorf("modbus: slaveID '%v' is not equal to '%v'", head.slaveID, pp.TcpHead.slaveID)
		}
	default:
		err = fmt.Errorf("modbus: modbus type '%v' is not supported", pp.ProtocolJson.ModbusType)
	}
	if err != nil {
		return ProtocolJsonStruct{}, err
	}
	pjs.SlaveID = slaveID //站点id
	pjs.FuncCode = pdu[0] //功能码

	// 根据功能码还原json
	switch pjs.FuncCode {
	case FuncCodeReadCoils, FuncCodeReadDiscreteInputs: //01 02

		pjs.Register = pp.ProtocolJson.Register
		pjs.RegisterLength = uint16(pdu[1]) //寄存器数量
		pjs.WRData = make([]uint16, pp.ProtocolJson.RegisterLength*8)
		for i := 0; i < int(pjs.RegisterLength); i++ {
			for j := 0; j < 8; j++ {
				if pdu[2+i]&(1<<uint(j)) > 0 {
					pjs.WRData[i*8+j] = 1
				} else {
					pjs.WRData[i*8+j] = 0
				}
			}
		}
		pp.Verify = pjs.SlaveID == pp.ProtocolJson.SlaveID
	case FuncCodeReadHoldingRegisters, FuncCodeReadInputRegisters: // 03 04
		pjs.Register = pp.ProtocolJson.Register
		pjs.RegisterLength = uint16(pdu[1]) //寄存器数量
		pjs.WRData = make([]uint16, pp.ProtocolJson.RegisterLength)
		for i := 0; i < int(pjs.RegisterLength); i++ {
			pjs.WRData[i] = binary.BigEndian.Uint16(pdu[2+i*2:])
		}
		pp.Verify = pjs.SlaveID == pp.ProtocolJson.SlaveID

	case FuncCodeWriteSingleCoil, FuncCodeWriteSingleRegister: //05 06
		pjs.Register = pp.ProtocolJson.Register
		pjs.RegisterLength = 1
		pjs.WRData = make([]uint16, 1)
		pjs.WRData[0] = binary.BigEndian.Uint16(pdu[3:])

		pp.Verify = bytes.Equal(pdu[1:], pp.Pdu.Data)

	case FuncCodeWriteMultipleCoil, FuncCodeWriteMultipleRegisters: //15 16
		pjs.Register = binary.BigEndian.Uint16(pdu[1:])
		pjs.RegisterLength = 1
		pjs.WRData = make([]uint16, 1)
		pjs.WRData[0] = binary.BigEndian.Uint16(pdu[3:])
		pp.Verify = pjs.SlaveID == pp.ProtocolJson.SlaveID
	default:
		err = fmt.Errorf("modbus: function code '%v' is not supported", pp.ProtocolJson.FuncCode)
	}
	return pjs, err
}
func (sf *protocolFrame) encodeRTUFrame(slaveID byte, pdu ProtocolDataUnit) ([]byte, error) {
	length := len(pdu.Data) + 4
	if length > rtuAduMaxSize {
		return nil, fmt.Errorf("modbus: length of data '%v' must not be bigger than '%v'", length, rtuAduMaxSize)
	}
	requestAdu := sf.adu[:0:length]
	requestAdu = append(requestAdu, slaveID, pdu.FuncCode)
	requestAdu = append(requestAdu, pdu.Data...)
	checksum := CRC16(requestAdu)
	requestAdu = append(requestAdu, byte(checksum), byte(checksum>>8))
	return requestAdu, nil
}

// decode extracts slaveID and PDU from RTU frame and verify CRC.
func decodeRTUFrame(adu []byte) (uint8, []byte, error) {
	if len(adu) < rtuAduMinSize { // Minimum size (including address, funcCode and CRC)
		return 0, nil, fmt.Errorf("modbus: response length '%v' does not meet minimum '%v'", len(adu), rtuAduMinSize)
	}
	// Calculate checksum
	crc, expect := CRC16(adu[:len(adu)-2]), binary.LittleEndian.Uint16(adu[len(adu)-2:])
	if crc != expect {
		return 0, nil, fmt.Errorf("modbus: response crc '%x' does not match expected '%x'", expect, crc)
	}
	// slaveID & PDU but pass crc
	return adu[0], adu[1 : len(adu)-2], nil
}

// encode slaveID & PDU to a ASCII frame,return adu
//
//	Start           : 1 char
//	slaveID         : 2 chars
//	---- data Unit ----
//	Function        : 2 chars
//	Data            : 0 up to 2x252 chars
//	---- checksum ----
//	LRC             : 2 chars
//	End             : 2 chars
func (sf *protocolFrame) encodeASCIIFrame(slaveID byte, pdu ProtocolDataUnit) ([]byte, error) {
	length := len(pdu.Data) + 3
	if length > asciiAduMaxSize {
		return nil, fmt.Errorf("modbus: length of data '%v' must not be bigger than '%v'", length, asciiAduMaxSize)
	}

	// Exclude the beginning colon and terminating CRLF pair characters
	lrcVal := new(LRC).
		Reset().
		Push(slaveID).Push(pdu.FuncCode).Push(pdu.Data...).
		Value()

	// real ascii frame to send,
	// including asciiStart + ( slaveID + functionCode + data + lrc ) + CRLF
	frame := sf.adu[: 0 : length*2+3]
	frame = append(frame, []byte(asciiStart)...) // the beginning colon characters
	// the real adu
	frame = append(frame,
		hexTable[slaveID>>4], hexTable[slaveID&0x0f], // slave ID
		hexTable[pdu.FuncCode>>4], hexTable[pdu.FuncCode&0x0f]) // pdu funcCode
	for _, v := range pdu.Data {
		frame = append(frame, hexTable[v>>4], hexTable[v&0x0f]) // pdu data
	}
	frame = append(frame, hexTable[lrcVal>>4], hexTable[lrcVal&0x0f]) // lrc value
	// terminating CRLF characters
	return append(frame, []byte(asciiEnd)...), nil
}

// decode extracts slaveID & PDU from ASCII frame and verify LRC.
func decodeASCIIFrame(adu []byte) (uint8, []byte, error) {
	if len(adu) < asciiAduMinSize+6 { // Minimum size (including address, function and LRC)
		return 0, nil, fmt.Errorf("modbus: response length '%v' does not meet minimum '%v'", len(adu), 9)
	}
	switch {
	case len(adu)%2 != 1: // Length excluding colon must be an even number
		return 0, nil, fmt.Errorf("modbus: response length '%v' is not an even number", len(adu)-1)
	case string(adu[0:len(asciiStart)]) != asciiStart: // First char must be a colons
		return 0, nil, fmt.Errorf("modbus: response frame '%x'... is not started with '%x'",
			string(adu[0:len(asciiStart)]), asciiStart)
	case string(adu[len(adu)-len(asciiEnd):]) != asciiEnd: // 2 last chars must be \r\n
		return 0, nil, fmt.Errorf("modbus: response frame ...'%x' is not ended with '%x'",
			string(adu[len(adu)-len(asciiEnd):]), asciiEnd)
	}

	// real adu  pass Start and CRLF
	dat := adu[1 : len(adu)-2]
	buf := make([]byte, hex.DecodedLen(len(dat)))
	length, err := hex.Decode(buf, dat)
	if err != nil {
		return 0, nil, err
	}
	// Calculate checksum
	lrcVal := new(LRC).Reset().Push(buf[:length-1]...).Value()
	if buf[length-1] != lrcVal { // LRC
		return 0, nil, fmt.Errorf("modbus: response lrc '%x' does not match expected '%x'", buf[length-1], lrcVal)
	}
	return buf[0], buf[1 : length-1], nil
}

// encode modbus application protocol header & pdu to TCP frame,return adu.
//
//	---- MBAP header ----
//	Transaction identifier: 2 bytes
//	Protocol identifier: 2 bytes
//	Length: 2 bytes
//	Unit identifier: 1 byte
//	---- data Unit ----
//	Function code: 1 byte
//	Data: n bytes
func (sf *protocolFrame) encodeTCPFrame(tid uint16, slaveID byte,
	pdu ProtocolDataUnit) (protocolTCPHeader, []byte, error) {
	length := tcpHeaderMbapSize + 1 + len(pdu.Data)
	if length > tcpAduMaxSize {
		return protocolTCPHeader{}, nil, fmt.Errorf("modbus: length of data '%v' must not be bigger than '%v'",
			length, tcpAduMaxSize)
	}

	head := protocolTCPHeader{
		tid,
		tcpProtocolIdentifier,
		uint16(2 + len(pdu.Data)), // sizeof(SlaveId) + sizeof(FuncCode) + Data
		slaveID,
	}

	// fill adu buffer
	adu := sf.adu[0:length]
	binary.BigEndian.PutUint16(adu, head.transactionID)  // MBAP Transaction identifier
	binary.BigEndian.PutUint16(adu[2:], head.protocolID) // MBAP Protocol identifier
	binary.BigEndian.PutUint16(adu[4:], head.length)     // MBAP Length
	adu[6] = head.slaveID                                // MBAP Unit identifier
	adu[tcpHeaderMbapSize] = pdu.FuncCode                // PDU funcCode
	copy(adu[tcpHeaderMbapSize+1:], pdu.Data)            // PDU data
	return head, adu, nil
}

// decode extracts tcpHeader & PDU from TCP frame:
//
//	---- MBAP header ----
//	Transaction identifier: 2 bytes
//	Protocol identifier: 2 bytes
//	Length: 2 bytes
//	Unit identifier: 1 byte
//	---- data Unit ----
//	Function        : 1 byte
//	Data            : 0 up to 252 bytes
func decodeTCPFrame(adu []byte) (protocolTCPHeader, []byte, error) {
	if len(adu) < tcpAduMinSize { // Minimum size (including MBAP, funcCode)
		return protocolTCPHeader{}, nil, fmt.Errorf("modbus: response length '%v' does not meet minimum '%v'",
			len(adu), tcpAduMinSize)
	}
	// Read length value in the header
	head := protocolTCPHeader{
		transactionID: binary.BigEndian.Uint16(adu),
		protocolID:    binary.BigEndian.Uint16(adu[2:]),
		length:        binary.BigEndian.Uint16(adu[4:]),
		slaveID:       adu[6],
	}

	pduLength := len(adu) - tcpHeaderMbapSize
	if pduLength != int(head.length-1) {
		return head, nil, fmt.Errorf("modbus: length in response '%v' does not match pdu data length '%v'",
			head.length-1, pduLength)
	}
	// The first byte after header is function code
	return head, adu[tcpHeaderMbapSize:], nil
}
